package com.ryan.mdp.config;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.ryan.mdp.service.MdpServiceFactoryBean;

public class MdpServiceBeanDefinationParser extends AbstractMdpBeanDefinationParser {

    private static final String CONTAINER_TYPE_ATTRIBUTE = "container-type";

    private static final String REF_ATTRIBUTE = "ref";

    private static final String SELECTOR_ATTRIBUTE = "selector";

    private static final String SERVER_ATTRIBUTE = "server";

    @Override
    protected Class<MdpServiceFactoryBean> getBeanClass(Element element) {
        return MdpServiceFactoryBean.class;
    }

    @Override
    protected void doParseSpecial(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) {
        AbstractBeanDefinition beanDefinition = builder.getRawBeanDefinition();
        beanDefinition.setBeanClass(MdpServiceFactoryBean.class);
        String containerType = element.getAttribute(CONTAINER_TYPE_ATTRIBUTE);
        if ("".equals(containerType) || "default".equals(containerType)) {
            builder.addPropertyValue("containerClass",
                    org.springframework.jms.listener.DefaultMessageListenerContainer.class);
        } else if ("simple".equals(containerType)) {
            builder.addPropertyValue("containerClass",
                    org.springframework.jms.listener.SimpleMessageListenerContainer.class);
        } else {
            parserContext.getReaderContext().error(
                    "Invalid 'container-type' attribute: only \"default\" and \"simple\" supported.", element);
        }

        // server: server attribute
        String serverName = element.getAttribute(SERVER_ATTRIBUTE);
        if (!"".equals(serverName)) {
            builder.addPropertyValue(SERVER_ATTRIBUTE, serverName);
        }

        // server: selector attribute
        String selector = element.getAttribute(SELECTOR_ATTRIBUTE);
        if (!"".equals(selector)) {
            builder.addPropertyValue(SELECTOR_ATTRIBUTE, selector);
        }

        // determine nested/referred beans
        Object target = null;
        if (element.hasAttribute(REF_ATTRIBUTE))
            target = new RuntimeBeanReference(element.getAttribute(REF_ATTRIBUTE));

        // element is considered parent
        NodeList nl = element.getChildNodes();

        // parse all sub elements
        // we iterate through them since we have to 'catch' the possible nested
        // bean which has an unknown name local name

        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element subElement = (Element) node;
                String name = subElement.getLocalName();
                if (BeanDefinitionParserDelegate.DESCRIPTION_ELEMENT.equals(name)) {
                    builder.getRawBeanDefinition().setDescription(subElement.getTextContent());
                }
                // nested bean reference/declaration
                else
                    target = parseBeanReference(element, subElement, parserContext, builder);
            }
        }

        // if we have a named bean use target_bean_name (so we postpone the
        // service creation)
        if (target instanceof RuntimeBeanReference) {
            builder.addPropertyValue("targetBeanName", ((RuntimeBeanReference) target).getBeanName());
        } else {
            // add target (can be either an object instance or a bean
            // definition)
            builder.addPropertyValue("target", target);
        }
    }

    // parse destinations

    // parse nested bean definition
    private Object parseBeanReference(Element parent, Element element, ParserContext parserContext,
            BeanDefinitionBuilder builder) {
        // check shortcut on the parent
        if (parent.hasAttribute(REF_ATTRIBUTE))
            parserContext.getReaderContext().error(
                    "nested bean definition/reference cannot be used when attribute 'ref' is specified", parent);
        return parsePropertySubElement(parserContext, element, builder.getBeanDefinition());
    }

    private Object parsePropertySubElement(ParserContext context, Element beanDef, BeanDefinition beanDefinition) {
        return context.getDelegate().parsePropertySubElement(beanDef, beanDefinition);
    }
}
